// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: xguo

#include <cxxopts.hpp>
#include <fstream>

#include "coin/proto/coin_order_gateway.pb.h"
#include "coin2/app/driver.h"
#include "coin2/base/config/app_config.h"
#include "coin2/base/config/args.h"
#include "coin2/base/log.h"
#include "coin2/base/time.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/strategy/strategy.h"

using namespace coin2::exchange::base::symbology;
using namespace coin2::base::config;
using namespace coin2::exchange::feed;
using namespace coin2::app;
using coin2::exchange::base::feed::FeedUpdate;
using coin2::exchange::base::order::OrderUpdate;

struct Statistic {
  std::string market;
  std::string exchange;
  int64_t timestamp;
  std::string product;
  int counts;
  double sum_amount_ask0_bid0;
  double sum_amount_1bp;
  double sum_amount_5bp;
  double sum_amount_10bp;
  double sum_spread;
  double sum_spread_1000000_krw;
  double sum_spread_2000000_krw;
  double sum_spread_5000000_krw;
  double sum_fill_amount;

  Statistic() {
    counts = 0;
    sum_amount_ask0_bid0 = 0;
    sum_amount_1bp = 0;
    sum_amount_5bp = 0;
    sum_amount_10bp = 0;
    sum_spread = 0;
    sum_spread_1000000_krw = 0;
    sum_spread_2000000_krw = 0;
    sum_spread_5000000_krw = 0;
    sum_fill_amount = 0;
  }

  const char* ToCsv() {
    static char buffer[1024];
    ::snprintf(
        buffer,
        sizeof(buffer),
        "%s,%s,%ld,%s,%d,%f,%f,%f,%f,%f,%f,%f,%f,%f\n",
        market.data(),
        exchange.data(),
        timestamp,
        product.data(),
        counts,
        sum_amount_ask0_bid0 / counts,
        sum_amount_1bp / counts,
        sum_amount_5bp / counts,
        sum_amount_10bp / counts,
        sum_spread / counts,
        sum_spread_1000000_krw / counts,
        sum_spread_2000000_krw / counts,
        sum_spread_5000000_krw / counts,
        sum_fill_amount);
    return buffer;
  }

  static const char* header() {
    static char buffer[1024];
    ::snprintf(
        buffer,
        sizeof(buffer),
        "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
        "market",
        "exchange",
        "timestamp",
        "product",
        "counts",
        "avg_qty_ask0_bid0",
        "avg_qty_1bp",
        "avg_qty_5bp",
        "avg_qty_10bp",
        "avg_qty_spread",
        "avg_qty_spread_1000000_krw",
        "avg_qty_spread_2000000_krw",
        "avg_qty_spread_5000000_krw",
        "sum_fill_amount");
    return buffer;
  }
};

class DumperStrategy : public ::coin2::strategy::IStrategy {
 public:
  DumperStrategy() {
    is_first_time = true;
  }

  void onTradeFeed(const FeedUpdate& upd) override {
    auto& trade = upd.trade();
    statistic_.sum_fill_amount += trade.fill_qty * trade.price;
    if (is_first_time) {
      statistic_.market = MarketType_Name(upd.market());
      statistic_.exchange = ExchangeType_Name(upd.exchange());
      statistic_.timestamp = upd.timestamp();
      statistic_.product = upd.product().relative_norm();
      is_first_time = false;
    }
  }

  void onBookFeed(const FeedUpdate& upd) override {
    auto& book = upd.book();
    statistic_.counts++;
    statistic_.sum_amount_ask0_bid0 +=
        book.Ask0()->qty * book.Ask0()->price + book.Bid0()->qty * book.Bid0()->price;
    // std::cout << book.Ask0()->price << " " << book.Bid0()->price << std::endl;
    double mid_price = (book.Ask0()->price + book.Bid0()->price) / 2;
    int bid_depth = book.BidSize();
    int ask_depth = book.AskSize();
    for (int i = 0; i < bid_depth; i++) {
      if (book.BidN(i)->price > mid_price * 0.9999) {
        statistic_.sum_amount_1bp += book.BidN(i)->qty * book.BidN(i)->price;
      }
      if (book.BidN(i)->price > mid_price * 0.9995) {
        statistic_.sum_amount_5bp += book.BidN(i)->qty * book.BidN(i)->price;
      }
      if (book.BidN(i)->price > mid_price * 0.999) {
        statistic_.sum_amount_10bp += book.BidN(i)->qty * book.BidN(i)->price;
      } else {
        // std::cout << i << " " << book.BidN(i)->price << std::endl;
        break;
      }
    }
    for (int i = 0; i < ask_depth; i++) {
      if (book.AskN(i)->price < mid_price * 1.0001) {
        statistic_.sum_amount_1bp += book.AskN(i)->qty * book.AskN(i)->price;
      }
      if (book.AskN(i)->price < mid_price * 1.0005) {
        statistic_.sum_amount_5bp += book.AskN(i)->qty * book.AskN(i)->price;
      }
      if (book.AskN(i)->price < mid_price * 1.001) {
        statistic_.sum_amount_10bp += book.AskN(i)->qty * book.AskN(i)->price;
      } else {
        // std::cout << i << " " << book.AskN(i)->price << std::endl;
        break;
      }
    }
    statistic_.sum_spread += (book.Ask0()->price - book.Bid0()->price) / mid_price * 10000;
    double sum_amount = 0;
    int bid_index = 0;
    int ask_index = 0;
    bool find_1000000 = false;
    bool find_2000000 = false;
    // std::cout << ask_depth << " " << bid_depth << std::endl;
    while (bid_index < bid_depth || ask_index < ask_depth) {
      double amount1 = book.AskN(ask_index)->qty * book.AskN(ask_index)->price;
      double amount2 = book.BidN(bid_index)->qty * book.BidN(bid_index)->price;
      if (amount1 > amount2) {
        sum_amount += amount1;
        ask_index++;
      } else {
        sum_amount += amount2;
        bid_index++;
      }
      ask_index = std::min(ask_depth - 1, ask_index);
      bid_index = std::min(bid_depth - 1, bid_index);
      if (sum_amount > 1000000 && !find_1000000) {
        statistic_.sum_spread_1000000_krw +=
            (book.AskN(ask_index)->price - book.BidN(bid_index)->price) / mid_price * 10000;
        find_1000000 = true;
      }
      if (sum_amount > 2000000 && !find_2000000) {
        statistic_.sum_spread_2000000_krw +=
            (book.AskN(ask_index)->price - book.BidN(bid_index)->price) / mid_price * 10000;
        find_2000000 = true;
      }
      if (sum_amount > 5000000) {
        statistic_.sum_spread_5000000_krw +=
            (book.AskN(ask_index)->price - book.BidN(bid_index)->price) / mid_price * 10000;
        break;
      }
    }
    if (sum_amount < 1000000) {
      statistic_.sum_spread_1000000_krw +=
          (book.AskN(ask_index)->price - book.BidN(bid_index)->price) / mid_price * 10000 * 1000000 / sum_amount;
    }
    if (sum_amount < 2000000) {
      statistic_.sum_spread_2000000_krw +=
          (book.AskN(ask_index)->price - book.BidN(bid_index)->price) / mid_price * 10000 * 2000000 / sum_amount;
    }
    if (sum_amount < 5000000) {
      statistic_.sum_spread_5000000_krw +=
          (book.AskN(ask_index)->price - book.BidN(bid_index)->price) / mid_price * 10000 * 5000000 / sum_amount;
    }
    // std::cout << "--------------------------- " << std::endl;
  }

  void onLiquidationFeed(const FeedUpdate&) override {}
  void onAccountInfo(const OrderUpdate&) override {}
  void onAccountOrder(const OrderUpdate&) override {}
  void onOrderLog(const coin::proto::OrderGatewayLog&) override {}

  Statistic statistic_;
  bool is_first_time;
};

int main(int argc, char* argv[]) {
  google::InitGoogleLogging(argv[0]);

  DriverConfig app;

  cxxopts::Options opt("Printer", "Print feed and order events.");
  AddDriverOptions(&opt);
  AddIntervalOptions(&opt);
  AddProductOptions(&opt);

  int tmp_argc = 3;
  auto res = opt.parse(tmp_argc, argv);
  ParseDriverOptions(res, &app);

  std::string feed_names[5] = {"feed-01.ap-northeast-2.aws", "feed-01.ap-northeast-1.aws",
	  "feed-02.ap-northeast-2.aws", "feed-05.ap-northeast-1.aws", "feed-06.ap-northeast-1.aws"};

  std::string symbols[10] = {"BTC-KRW", "ETH-KRW", "XRP-KRW", "BCH-KRW", "BSV-KRW", "LTC-KRW",
	  "EOS-KRW", "TRX-KRW", "ETC-KRW", "XLM-KRW"};
/*
  for (int j=0;j<10;j++) {
    for (auto& [mea1, cfg1] : app.mutable_feed()->mutable_exchanges()) {
      cfg1->->set_machine(feed_names[i]);
*/
  for (int i=0;i<5;i++) {
    app.mutable_feed()->mutable_common()->mutable_archive()->set_machine(feed_names[i]);

  app.mutable_feed()->mutable_common()->mutable_archive()->mutable_interval()->set_start(std::string(argv[3]));
  ParseIntervalOptions(
      res,
      app.mutable_feed()->mutable_common()->mutable_archive()->mutable_interval());
  ParseProductOptions(res, app.mutable_feed());

  // LOG(INFO) << app.DebugString();

  DumperStrategy printer;  // printer unused if feed-writer
  Driver driver(app, &printer);
  driver.Run();
  for (auto& [mea, cfg] : app.feed().exchanges()) {
    for (auto& norm : cfg.products().norms()) {
      std::ofstream myfile;
      auto file_name = mea + "_" + norm + "_" + app.feed().common().archive().interval().start();
      std::cout << "/remote/iosg/home-2/linchuan/data/" + file_name + ".txt" << std::endl;
      myfile.open ("/remote/iosg/home-2/linchuan/data/" + file_name + ".txt");
      myfile << printer.statistic_.ToCsv();
      myfile.close();
    }
  }

  if (printer.statistic_.counts > 1000) break;

  }//*/
  // std::cout << printer.statistic_.header();

  return 0;
}
